// SPDX-License-Identifier: MIT
/*
 * Copyright (c) 2010 Serge Zaitsev
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jsmn.h>
#include <linux/overflow.h>
#include <malloc.h>

/**
 * Allocates a fresh unused token from the token pool.
 */
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
				   const size_t num_tokens) {
	jsmntok_t *tok;
	if (parser->toknext >= num_tokens) {
		return NULL;
	}
	tok = &tokens[parser->toknext++];
	tok->start = tok->end = -1;
	tok->size = 0;
#ifdef JSMN_PARENT_LINKS
	tok->parent = -1;
#endif
	return tok;
}

/**
 * Fills token type and boundaries.
 */
static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
			    const int start, const int end) {
	token->type = type;
	token->start = start;
	token->end = end;
	token->size = 0;
}

/**
 * Fills next available token with JSON primitive.
 */
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
				const size_t len, jsmntok_t *tokens,
				const size_t num_tokens) {
	jsmntok_t *token;
	int start;

	start = parser->pos;

	for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
		switch (js[parser->pos]) {
#ifndef JSMN_STRICT
			/* In strict mode primitive must be followed by "," or "}" or "]" */
		case ':':
#endif
		case '\t':
		case '\r':
		case '\n':
		case ' ':
		case ',':
		case ']':
		case '}':
			goto found;
		default:
			/* to quiet a warning from gcc*/
			break;
		}
		if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
			parser->pos = start;
			return JSMN_ERROR_INVAL;
		}
	}
#ifdef JSMN_STRICT
	/* In strict mode primitive must be followed by a comma/object/array */
	parser->pos = start;
	return JSMN_ERROR_PART;
#endif

found:
	if (tokens == NULL) {
		parser->pos--;
		return 0;
	}
	token = jsmn_alloc_token(parser, tokens, num_tokens);
	if (token == NULL) {
		parser->pos = start;
		return JSMN_ERROR_NOMEM;
	}
	jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
	token->parent = parser->toksuper;
#endif
	parser->pos--;
	return 0;
}

/**
 * Fills next token with JSON string.
 */
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
			     const size_t len, jsmntok_t *tokens,
			     const size_t num_tokens) {
	jsmntok_t *token;

	int start = parser->pos;

	/* Skip starting quote */
	parser->pos++;

	for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
		char c = js[parser->pos];

		/* Quote: end of string */
		if (c == '\"') {
			if (tokens == NULL) {
				return 0;
			}
			token = jsmn_alloc_token(parser, tokens, num_tokens);
			if (token == NULL) {
				parser->pos = start;
				return JSMN_ERROR_NOMEM;
			}
			jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
			token->parent = parser->toksuper;
#endif
			return 0;
		}

		/* Backslash: Quoted symbol expected */
		if (c == '\\' && parser->pos + 1 < len) {
			int i;
			parser->pos++;
			switch (js[parser->pos]) {
				/* Allowed escaped symbols */
			case '\"':
			case '/':
			case '\\':
			case 'b':
			case 'f':
			case 'r':
			case 'n':
			case 't':
				break;
				/* Allows escaped symbol \uXXXX */
			case 'u':
				parser->pos++;
				for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
				     i++) {
					/* If it isn't a hex character we have an error */
					if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
					      (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
					      (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
						parser->pos = start;
						return JSMN_ERROR_INVAL;
					}
					parser->pos++;
				}
				parser->pos--;
				break;
				/* Unexpected symbol */
			default:
				parser->pos = start;
				return JSMN_ERROR_INVAL;
			}
		}
	}
	parser->pos = start;
	return JSMN_ERROR_PART;
}

/**
 * Parse JSON string and fill tokens.
 */
JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
			jsmntok_t *tokens, const unsigned int num_tokens) {
	int r;
	int i;
	jsmntok_t *token;
	int count = parser->toknext;

	for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
		char c;
		jsmntype_t type;

		c = js[parser->pos];
		switch (c) {
		case '{':
		case '[':
			count++;
			if (tokens == NULL) {
				break;
			}
			token = jsmn_alloc_token(parser, tokens, num_tokens);
			if (token == NULL) {
				return JSMN_ERROR_NOMEM;
			}
			if (parser->toksuper != -1) {
				jsmntok_t *t = &tokens[parser->toksuper];
#ifdef JSMN_STRICT
				/* In strict mode an object or array can't become a key */
				if (t->type == JSMN_OBJECT) {
					return JSMN_ERROR_INVAL;
				}
#endif
				t->size++;
#ifdef JSMN_PARENT_LINKS
				token->parent = parser->toksuper;
#endif
			}
			token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
			token->start = parser->pos;
			parser->toksuper = parser->toknext - 1;
			break;
		case '}':
		case ']':
			if (tokens == NULL) {
				break;
			}
			type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
			if (parser->toknext < 1) {
				return JSMN_ERROR_INVAL;
			}
			token = &tokens[parser->toknext - 1];
			for (;;) {
				if (token->start != -1 && token->end == -1) {
					if (token->type != type) {
						return JSMN_ERROR_INVAL;
					}
					token->end = parser->pos + 1;
					parser->toksuper = token->parent;
					break;
				}
				if (token->parent == -1) {
					if (token->type != type || parser->toksuper == -1) {
						return JSMN_ERROR_INVAL;
					}
					break;
				}
				token = &tokens[token->parent];
			}
#else
			for (i = parser->toknext - 1; i >= 0; i--) {
				token = &tokens[i];
				if (token->start != -1 && token->end == -1) {
					if (token->type != type) {
						return JSMN_ERROR_INVAL;
					}
					parser->toksuper = -1;
					token->end = parser->pos + 1;
					break;
				}
			}
			/* Error if unmatched closing bracket */
			if (i == -1) {
				return JSMN_ERROR_INVAL;
			}
			for (; i >= 0; i--) {
				token = &tokens[i];
				if (token->start != -1 && token->end == -1) {
					parser->toksuper = i;
					break;
				}
			}
#endif
			break;
		case '\"':
			r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
			if (r < 0) {
				return r;
			}
			count++;
			if (parser->toksuper != -1 && tokens != NULL) {
				tokens[parser->toksuper].size++;
			}
			break;
		case '\t':
		case '\r':
		case '\n':
		case ' ':
			break;
		case ':':
			parser->toksuper = parser->toknext - 1;
			break;
		case ',':
			if (tokens != NULL && parser->toksuper != -1 &&
			    tokens[parser->toksuper].type != JSMN_ARRAY &&
			    tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
				parser->toksuper = tokens[parser->toksuper].parent;
#else
				for (i = parser->toknext - 1; i >= 0; i--) {
					if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
						if (tokens[i].start != -1 && tokens[i].end == -1) {
							parser->toksuper = i;
							break;
						}
					}
				}
#endif
			}
			break;
#ifdef JSMN_STRICT
			/* In strict mode primitives are: numbers and booleans */
		case '-':
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case 't':
		case 'f':
		case 'n':
			/* And they must not be keys of the object */
			if (tokens != NULL && parser->toksuper != -1) {
				const jsmntok_t *t = &tokens[parser->toksuper];
				if (t->type == JSMN_OBJECT ||
				    (t->type == JSMN_STRING && t->size != 0)) {
					return JSMN_ERROR_INVAL;
				}
			}
#else
			/* In non-strict mode every unquoted value is a primitive */
		default:
#endif
			r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
			if (r < 0) {
				return r;
			}
			count++;
			if (parser->toksuper != -1 && tokens != NULL) {
				tokens[parser->toksuper].size++;
			}
			break;

#ifdef JSMN_STRICT
			/* Unexpected char in strict mode */
		default:
			return JSMN_ERROR_INVAL;
#endif
		}
	}

	if (tokens != NULL) {
		for (i = parser->toknext - 1; i >= 0; i--) {
			/* Unmatched opened object or array */
			if (tokens[i].start != -1 && tokens[i].end == -1) {
				return JSMN_ERROR_PART;
			}
		}
	}

	return count;
}

/**
 * Creates a new parser based over a given buffer with an array of tokens
 * available.
 */
JSMN_API void jsmn_init(jsmn_parser *parser) {
	parser->pos = 0;
	parser->toknext = 0;
	parser->toksuper = -1;
}

/**
 * Parse JSON string and fill tokens into self-allocated buffer.
 */
JSMN_API jsmntok_t *jsmn_parse_alloc(const char *js, const size_t len,
				     unsigned int *num_tokens)
{

	ssize_t token_count;
	jsmn_parser parser;
	jsmntok_t *tokens;
	int ret;

	jsmn_init(&parser);

	/* Figure out how many tokens we need. */
	ret = jsmn_parse(&parser, js, len, NULL, 0);
	if (ret < 0)
		return NULL;

	token_count = ret;

	tokens = malloc(size_mul(token_count, sizeof(jsmntok_t)));
	if (!tokens)
		return NULL;

	jsmn_init(&parser);
	ret = jsmn_parse(&parser, js, len, tokens, token_count);
	if (ret < 0) {
		free(tokens);
		return NULL;
	}

	if (num_tokens)
		*num_tokens = ret;
	return tokens;
}
JSMN_API bool jsmn_eq(const char *val, const char *json, const jsmntok_t *token)
{
	size_t token_size = jsmn_token_size(token);
	return strlen(val) == token_size
		&& strncmp(json + token->start, val, token_size) == 0;
}

JSMN_API bool jsmn_str_eq(const char *str, const char *json, const jsmntok_t *token)
{
	return token->type == JSMN_STRING && jsmn_eq(str, json, token);
}

static bool jsmn_case_eq(const char *val, const char *json, const jsmntok_t *token)
{
	size_t token_size = jsmn_token_size(token);
	return strlen(val) == token_size
		&& strncasecmp(json + token->start, val, token_size) == 0;
}

JSMN_API bool jsmn_strcase_eq(const char *str, const char *json, const jsmntok_t *token)
{
	return token->type == JSMN_STRING && jsmn_case_eq(str, json, token);
}

JSMN_API const jsmntok_t *jsmn_skip_value(const jsmntok_t *tokens)
{
	int max_index = tokens[0].end;
	do {
		++tokens;
	} while (tokens[0].start < max_index);
	return &tokens[0];
}

JSMN_API const jsmntok_t *jsmn_find_value(const char *key, const char *json,
					  const jsmntok_t *tokens)
{
	int items;
	if (tokens[0].type != JSMN_OBJECT || tokens[0].size == 0)
		return NULL;

	items = tokens[0].size;
	++tokens;

	do {
		if (jsmn_str_eq(key, json, tokens))
			return &tokens[1];
		tokens = --items ? jsmn_skip_value(&tokens[1]) : NULL;
	} while (tokens);

	return NULL;
}

JSMN_API const jsmntok_t *jsmn_locate(const char *path[], const char *json,
				      const jsmntok_t *tokens)
{
	int i = 0;
	while (path[i] != NULL) {
		const jsmntok_t *value = jsmn_find_value(path[i], json, tokens);
		if (!value)
			return NULL;

		switch (value->type) {
		case JSMN_OBJECT:
		case JSMN_ARRAY:
			tokens = value;
			++i;
			break;
		case JSMN_UNDEFINED:
		case JSMN_STRING:
		case JSMN_PRIMITIVE:
			return value;
		}
	}

	return tokens;
}

JSMN_API char *jsmn_strdup(const char *path[], const char *json,
			   const jsmntok_t *tokens)
{
	const jsmntok_t *node;
	int value_size;
	char *value;

	node = jsmn_locate(path, json, tokens);
	if (!node || node->type != JSMN_STRING)
		return NULL;

	value_size = jsmn_token_size(node);
	value = malloc(value_size + 1);
	if (value) {
		strncpy(value, json + node->start, value_size);
		value[value_size] = '\0';
	}

	return value;
}
